/*
 * Path.scala
 */

package acorn.jash

import java.io.File

import acorn.jash.Shell._

/**
 * This class represents a file path used in Jash. It follows
 * the format of UNIX, i.e. slash(/) as the path separator;
 * a single slash(/) as the root directory. <p/>
 *
 * On Windows, an absolute path starts with double slashes
 * followed by the drive letter, then slash and directory name.
 * For example, c:\Windows is represented as //c/Windows.
 *
 * Instance of this class is read only.<p/>
 *
 * Case-sensitive path, to be decided.
 *
 * @param: p is the platform dependent path to be used to create
 *         a Path object. It must be an absolute path, i.e.
 *         for Windows, it must start with a drive letter, then
 *         colon, then backward-slash and directory name, which
 *         is represented by regular expression:
 *         ^([a-zA-Z]:)?(\\[a-zA-Z_\s]*)*\\?$
 */
class Path (p:String) {

  private def this() = this("c:")

  /**
   * Return the platform dependent path represented in String.
   */
  def realpath:String = {
    val dirs = (compo.head+":")::compo.tail
    if (dirs.length == 1) dirs.head + "\\"
    else dirs.reduceLeft(_+"\\"+_)
  }

  /**
   * Return the path used in Jash, as //c/Windows
   */
  def path:String = ("/" /: compo)(_+"/"+_)


  /**
   * Return the last part of the Path, it could be a
   * file or a directory.
   */
  def filename:String = if (compo.length == 1) "" else compo.last
    

  /**
   * Return the parent path as a <code>Path</code> object.
   */
  def parent:Path = {
    val par = new Path
    if (compo.length > 1) par.compo = compo.init
    else throw new AlreadyAtRootException
    par
  }

  /**
   * Return the ancestor of the Path that REALLY exists
   * (possible including the path itself).
   */
  def up:Path = if (asFile.exists) this else parent.up


  /**
   * Return the drive letter for Windows
   */
  def drive = new Path(compo.head + ":")


  def ==(right:Path):Boolean = compo == right.compo


  def asFile = new File(realpath)


  /**
   * Return a new <code>Path<code> by calculating
   * the given argument and the path represented by
   * the current Path object.
   *
   * @param: to is the given path to change to. It should
   *         be separated by slash(/). If it
   *         starts with double slash, i.e. //, it's
   *         absolute path starting with the drive letter;
   *         if it starts with a single slash, i.e. /,
   *         it's an absolute path in the current drive
   *         which is c by default; if it starts with
   *         "../", it's the parent of the current directory;
   *         otherwise, it's the current directory.
   */
  /*
  def changeTo(to:String):Path = {
    if (null == to || 0 == to.length) return this

    val p = new Path
    p.compo = 
      if (!(Path.DOUBLE_SLASHES.findAllIn(to).isEmpty)) 
        to.substring(2).split("/").toList
      else if (!(Path.SINGLE_SLASH.findAllIn(to).isEmpty))
        if (to == "/") List(compo.head)
        else compo.head::to.substring(1).split("/").toList
      else if (to startsWith "~") {
        Path.home.compo:::
          to.substring(if (to == "~") 1 else 2).split("/").toList
      }
      else {
        def changeTo(cur:List[String], to:String):List[String] = {
          if (to startsWith "../") {
            if (cur.length == 1) throw new InvalidPathException(to)
            else changeTo(cur.init, to substring 3)
          }
          else if (to == "..") {
            if (cur.length == 1) throw new InvalidPathException(to)
            else cur.init
          }
          else cur:::to.split("/").toList
        }
        changeTo(compo, to)
      }
    p
  }
  */

  def changeTo(to:String):Path = {

    ("to => " + to)
  
    if (null == to || 0 == to.length) return this

    if (!(Path.WIN_ABS_PATH.findAllIn(to).isEmpty)) {
      return new Path(to)
    }

    def changeTo(dirs:List[String], toPath:Array[String]):List[String] = {
      if (toPath.length == 0) return dirs

      val h = toPath(0)
      if (h == ".") changeTo(dirs, toPath.tail)
      else if (h == "..") changeTo(dirs.init, toPath.tail)
      else changeTo((h::(dirs.reverse)).reverse, toPath.tail)
    }

    val path = new Path
    path.compo = changeTo(wd.compo, to split """\\""")
    path
  }



  /**
   * Internal storage a specific path; every entry represents
   * one component of the full path, i.e. directory(file) name.
   * For example, for c:\Windows, the first entry is 'c', the
   * second is Windows.
   */
  private var compo:List[String] = 
    if (!(Path.WIN_ABS_PATH.findAllIn(p).isEmpty)) {
      val n = p.split("""\\""").toList
      if (n.length == 0) "c"::Nil // p is / only
      else {
        val drv = if (n(0) == "") "c" // p is /abc
                  else n(0) substring (0,1)
        if (n.length == 1) drv::Nil else drv::n.tail
      }
    }
    else 
      throw new JashException("Not a valid path: " + p)
}


class InvalidPathException(path:String)
  extends JashException("Invalid path: " + path)

class FileNotFoundException(file:String)
  extends JashException("[" + file + "] is not found.")

class AlreadyAtRootException()
  extends JashException("At root.")

object Path {
  /**
   * Regular expression pattern for a Windows absolute path.
   */
  import scala.util.matching.Regex
  val WIN_ABS_PATH = """^([a-zA-Z_0-9-]:)?(\\[a-zA-Z_0-9-\.]*)*\\?$""".r

  /**
   * The Jash representation of an absolute path starting with
   * drive letter, i.e. //c/Windows
   */
  val DOUBLE_SLASHES = """^//[a-zA-Z_0-9-]{1}(/[a-zA-Z_0-9-\.]*)*$""".r


  /**
   * A path starting with a single slash stands for an absolute
   * path under the current drive
   */
  val SINGLE_SLASH = """^/([a-zA-Z_0-9-]*|([a-zA-Z_0-9-\.]+/)*[a-zA-Z_0-9-\.]*)$""".r
  
  import java.lang.System._
  val home:Path = {
    val h = getenv("HOME").toLowerCase
    new Path(h)
  }

  def translate(arg:String) = winp(env(arg))
  

  /**
   * To Windows Path 
   */
  def winp(p:String):String = p match {
    case null => ""
    case _ => {

      val j = p indexOf "~"
      val pp = if (j == -1) p
               else p.substring(0,j) + home.realpath + {
                 val p3 = p.substring(j+1)
                 if (p3.length > 0) "\\" + p3 else ""
               }

      val i = pp indexOf jsFileSeparator
      debug("i => " + i)
      i match {
        case -1 => pp
        case _ => pp.substring(0, i) + "\\" +
                  (if (i == pp.length -1) "" else winp(pp.substring(i+1)))
      }
    }
  }


  def env(arg:String):String = {
    val s = arg indexOf "$"
    if (s == -1) arg
    else {
      val e1 = arg.indexOf("$", s + 1)
      val e2 = arg.indexOf("/", s + 1)
      // println("e1 => " + e1 + ", e2 => " + e2)
      val e = if (e1 == -1 && e2 == -1) arg.length
              else if (e1 == -1) e2
              else if (e2 == -1) e1
              else if (e1 < e2) e1 else e2
      debug("e => " + e)
      val v = getenv(arg.substring(s + 1, e))
      if (null == v) arg
      else
        arg.substring(0, s) + v + (if (e == arg.length || e + 1 == arg.length) ""
                                   else env(arg substring e))
    }
  }

  val fileSeparator = getProperty("file.separator")

  val separator = getProperty("path.separator")

  val lineSeparator = getProperty("line.separator")

  val jsFileSeparator = "/"

  val jsSeparator = ":"

  val jsLineSeparator = "\n"
}

